En djupdykning i Reacts useSyncExternalStore-hook för sömlös integration med externa datakÀllor och state-hanteringsbibliotek. LÀr dig hantera delat state effektivt.
React useSyncExternalStore: BemÀstra integrationen av externt state
Reacts useSyncExternalStore-hook, som introducerades i React 18, erbjuder ett kraftfullt och effektivt sÀtt att integrera externa datakÀllor och state-hanteringsbibliotek i dina React-komponenter. Denna hook lÄter komponenter prenumerera pÄ förÀndringar i externa stores, vilket sÀkerstÀller att UI:t alltid Äterspeglar den senaste datan samtidigt som prestandan optimeras. Denna guide ger en omfattande översikt över useSyncExternalStore, och tÀcker dess kÀrnkoncept, anvÀndningsmönster och bÀsta praxis.
FörstÄ behovet av useSyncExternalStore
I mÄnga React-applikationer kommer du att stöta pÄ scenarier dÀr state behöver hanteras utanför komponenttrÀdet. Detta Àr ofta fallet nÀr man hanterar:
- Tredjepartsbibliotek: Integrering med bibliotek som hanterar sitt eget state (t.ex. en databasanslutning, ett webblÀsar-API eller en fysikmotor).
- Delat state över komponenter: Hantera state som behöver delas mellan komponenter som inte Àr direkt relaterade (t.ex. anvÀndarautentiseringsstatus, applikationsinstÀllningar eller en global hÀndelsebuss).
- Externa datakÀllor: HÀmta och visa data frÄn externa API:er eller databaser.
Traditionella state-hanteringslösningar som useState och useReducer Àr vÀl lÀmpade för att hantera lokalt komponent-state. De Àr dock inte utformade för att hantera externt state effektivt. Att anvÀnda dem direkt med externa datakÀllor kan leda till prestandaproblem, inkonsekventa uppdateringar och komplex kod.
useSyncExternalStore hanterar dessa utmaningar genom att erbjuda ett standardiserat och optimerat sÀtt att prenumerera pÄ förÀndringar i externa stores. Det sÀkerstÀller att komponenter endast renderas om nÀr relevant data Àndras, vilket minimerar onödiga uppdateringar och förbÀttrar den övergripande prestandan.
KĂ€rnkoncepten i useSyncExternalStore
useSyncExternalStore tar tre argument:
subscribe: En funktion som tar en callback som argument och prenumererar pÄ den externa storen. Callbacken kommer att anropas nÀr storens data Àndras.getSnapshot: En funktion som returnerar en ögonblicksbild av datan frÄn den externa storen. Denna funktion bör returnera ett stabilt vÀrde som React kan anvÀnda för att avgöra om datan har Àndrats. Den mÄste vara ren och snabb.getServerSnapshot(valfri): En funktion som returnerar det initiala vÀrdet för storen under server-side rendering. Detta Àr avgörande för att sÀkerstÀlla att den initiala HTML-koden matchar den klient-sidiga renderingen. Det anvÀnds ENDAST i server-side rendering-miljöer. Om den utelÀmnas i en klient-sidig miljö, anvÀnder dengetSnapshotistÀllet. Det Àr viktigt att detta vÀrde aldrig Àndras efter att det initialt har renderats pÄ serversidan.
HÀr Àr en genomgÄng av varje argument:
1. subscribe
subscribe-funktionen ansvarar för att etablera en anslutning mellan React-komponenten och den externa storen. Den tar emot en callback-funktion, som den ska anropa nÀr storens data Àndras. Denna callback anvÀnds vanligtvis för att utlösa en om-rendering av komponenten.
Exempel:
const subscribe = (callback) => {
store.addListener(callback);
return () => {
store.removeListener(callback);
};
};
I detta exempel lÀgger store.addListener till callbacken i storens lista över lyssnare. Funktionen returnerar en rensningsfunktion som tar bort lyssnaren nÀr komponenten avmonteras, vilket förhindrar minneslÀckor.
2. getSnapshot
getSnapshot-funktionen ansvarar för att hÀmta en ögonblicksbild av datan frÄn den externa storen. Denna ögonblicksbild ska vara ett stabilt vÀrde som React kan anvÀnda för att avgöra om datan har Àndrats. React anvÀnder Object.is för att jÀmföra den nuvarande ögonblicksbilden med den föregÄende. DÀrför mÄste den vara snabb och det rekommenderas starkt att den returnerar ett primitivt vÀrde (strÀng, nummer, boolean, null, eller undefined).
Exempel:
const getSnapshot = () => {
return store.getData();
};
I detta exempel returnerar store.getData den aktuella datan frÄn storen. React kommer att jÀmföra detta vÀrde med det föregÄende vÀrdet för att avgöra om komponenten behöver renderas om.
3. getServerSnapshot (Valfri)
getServerSnapshot-funktionen Àr endast relevant nÀr server-side rendering (SSR) anvÀnds. Denna funktion anropas under den initiala server-renderingen, och dess resultat anvÀnds som det initiala vÀrdet för storen innan hydrering sker pÄ klienten. Att returnera konsekventa vÀrden Àr avgörande för framgÄngsrik SSR.
Exempel:
const getServerSnapshot = () => {
return store.getInitialDataForServer();
};
I detta exempel returnerar `store.getInitialDataForServer` den initiala datan som Àr lÀmplig för server-side rendering.
GrundlÀggande anvÀndningsexempel
LÄt oss titta pÄ ett enkelt exempel dÀr vi har en extern store som hanterar en rÀknare. Vi kan anvÀnda useSyncExternalStore för att visa rÀknarens vÀrde i en React-komponent:
// Extern store
const createStore = (initialValue) => {
let value = initialValue;
const listeners = new Set();
const subscribe = (listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
};
const getSnapshot = () => value;
const setState = (newValue) => {
value = newValue;
listeners.forEach((listener) => listener());
};
return {
subscribe,
getSnapshot,
setState,
};
};
const counterStore = createStore(0);
// React-komponent
import React from 'react';
import { useSyncExternalStore } from 'react';
function Counter() {
const count = useSyncExternalStore(counterStore.subscribe, counterStore.getSnapshot);
const increment = () => {
counterStore.setState(count + 1);
};
return (
<div>
<p>RĂ€kning: {count}</p>
<button onClick={increment}>Ăka</button>
</div>
);
}
export default Counter;
I detta exempel skapar createStore en enkel extern store som hanterar ett rÀknarvÀrde. Counter-komponenten anvÀnder useSyncExternalStore för att prenumerera pÄ förÀndringar i storen och visa det aktuella vÀrdet. NÀr man klickar pÄ inkrementeringsknappen uppdaterar setState-funktionen storens vÀrde, vilket utlöser en om-rendering av komponenten.
Integration med State-hanteringsbibliotek
useSyncExternalStore Àr sÀrskilt anvÀndbar för att integrera med state-hanteringsbibliotek som Zustand, Jotai och Recoil. Dessa bibliotek erbjuder sina egna mekanismer för att hantera state, och useSyncExternalStore lÄter dig sömlöst integrera dem i dina React-komponenter.
HÀr Àr ett exempel pÄ integration med Zustand:
import { useStore } from 'zustand';
import { create } from 'zustand';
// Zustand store
const useBoundStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}));
// React-komponent
function Counter() {
const count = useStore(useBoundStore, (state) => state.count);
const increment = useStore(useBoundStore, (state) => state.increment);
return (
<div>
<p>RĂ€kning: {count}</p>
<button onClick={increment}>Ăka</button>
</div>
);
}
export default Counter;
Zustand förenklar skapandet av en store. Dess interna subscribe- och getSnapshot-implementationer anvÀnds implicit nÀr du prenumererar pÄ ett visst state.
HÀr Àr ett exempel pÄ integration med Jotai:
import { atom, useAtom } from 'jotai'
// Jotai-atom
const countAtom = atom(0)
// React-komponent
function Counter() {
const [count, setCount] = useAtom(countAtom)
return (
<div>
<p>RĂ€kning: {count}</p>
<button onClick={() => setCount(count + 1)}>Ăka</button>
</div>
)
}
export default Counter;
Jotai anvÀnder atomer för att hantera state. useAtom hanterar internt prenumerationen och snapshottingen.
Prestandaoptimering
useSyncExternalStore erbjuder flera mekanismer för att optimera prestandan:
- Selektiva uppdateringar: React renderar endast om komponenten nÀr vÀrdet som returneras av
getSnapshotÀndras. Detta sÀkerstÀller att onödiga om-renderingar undviks. - Batch-uppdateringar: React samlar uppdateringar frÄn flera externa stores i en enda om-rendering. Detta minskar antalet om-renderingar och förbÀttrar den övergripande prestandan.
- Undviker inaktuella closures:
useSyncExternalStoresÀkerstÀller att komponenten alltid har tillgÄng till den senaste datan frÄn den externa storen, Àven nÀr man hanterar asynkrona uppdateringar.
För att ytterligare optimera prestandan, övervÀg följande bÀsta praxis:
- Minimera mÀngden data som returneras av
getSnapshot: Returnera endast den data som faktiskt behövs av komponenten. Detta minskar mÀngden data som behöver jÀmföras och förbÀttrar effektiviteten i uppdateringsprocessen. - AnvÀnd memoization-tekniker: Memoize-resultaten av dyra berÀkningar eller datatransformationer. Detta kan förhindra onödiga omberÀkningar och förbÀttra prestandan.
- Undvik onödiga prenumerationer: Prenumerera endast pÄ den externa storen nÀr komponenten faktiskt Àr synlig. Detta kan minska antalet aktiva prenumerationer och förbÀttra den övergripande prestandan.
- SÀkerstÀll att
getSnapshotreturnerar ett nytt *stabilt* objekt endast om datan Àndrats: Undvik att skapa nya objekt/arrayer/funktioner om den underliggande datan faktiskt inte har Àndrats. Returnera samma objekt med referens om möjligt.
Server-Side Rendering (SSR) med useSyncExternalStore
NÀr man anvÀnder useSyncExternalStore med server-side rendering (SSR) Àr det avgörande att tillhandahÄlla en getServerSnapshot-funktion. Denna funktion sÀkerstÀller att den initiala HTML-koden som renderas pÄ servern matchar den klient-sidiga renderingen, vilket förhindrar hydreringsfel och förbÀttrar anvÀndarupplevelsen.
HÀr Àr ett exempel pÄ hur man anvÀnder getServerSnapshot:
const createStore = (initialValue) => {
let value = initialValue;
const listeners = new Set();
const subscribe = (listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
};
const getSnapshot = () => value;
const getServerSnapshot = () => initialValue; // Viktigt för SSR
const setState = (newValue) => {
value = newValue;
listeners.forEach((listener) => listener());
};
return {
subscribe,
getSnapshot,
getServerSnapshot,
setState,
};
};
const counterStore = createStore(0);
// React-komponent
import React from 'react';
import { useSyncExternalStore } from 'react';
function Counter() {
const count = useSyncExternalStore(counterStore.subscribe, counterStore.getSnapshot, counterStore.getServerSnapshot);
const increment = () => {
counterStore.setState(count + 1);
};
return (
<div>
<p>RĂ€kning: {count}</p>
<button onClick={increment}>Ăka</button>
</div>
);
}
export default Counter;
I detta exempel returnerar getServerSnapshot det initiala vÀrdet för rÀknaren. Detta sÀkerstÀller att den initiala HTML-koden som renderas pÄ servern matchar den klient-sidiga renderingen. getServerSnapshot bör returnera ett stabilt och förutsÀgbart vÀrde. Den bör ocksÄ utföra samma logik som getSnapshot-funktionen pÄ servern. Undvik att komma Ät webblÀsarspecifika API:er eller globala variabler i getServerSnapshot.
Avancerade anvÀndningsmönster
useSyncExternalStore kan anvÀndas i en mÀngd avancerade scenarier, inklusive:
- Integration med webblÀsar-API:er: Prenumerera pÄ förÀndringar i webblÀsar-API:er som
localStorageellernavigator.onLine. - Skapa anpassade hooks: Kapsla in logiken för att prenumerera pÄ en extern store i en anpassad hook.
- AnvÀnda med Context API: Kombinera
useSyncExternalStoremed Reacts Context API för att tillhandahÄlla delat state till ett komponenttrÀd.
LÄt oss titta pÄ ett exempel dÀr vi skapar en anpassad hook för att prenumerera pÄ localStorage:
import { useSyncExternalStore } from 'react';
function useLocalStorage(key, initialValue) {
const getSnapshot = () => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error("Fel vid hÀmtning av vÀrde frÄn localStorage:", error);
return initialValue;
}
};
const subscribe = (callback) => {
window.addEventListener('storage', callback);
return () => window.removeEventListener('storage', callback);
};
const setItem = (value) => {
try {
window.localStorage.setItem(key, JSON.stringify(value));
window.dispatchEvent(new Event('storage')); // Utlös manuellt lagringshÀndelse för uppdateringar pÄ samma sida
} catch (error) {
console.error("Fel vid instÀllning av vÀrde i localStorage:", error);
}
};
const serverSnapshot = () => initialValue;
const storedValue = useSyncExternalStore(subscribe, getSnapshot, serverSnapshot);
return [storedValue, setItem];
}
export default useLocalStorage;
I detta exempel Àr useLocalStorage en anpassad hook som prenumererar pÄ förÀndringar i localStorage. Den anvÀnder useSyncExternalStore för att hantera prenumerationen och hÀmta det aktuella vÀrdet frÄn localStorage. Den skickar ocksÄ korrekt ett lagringshÀndelse (storage event) för att sÀkerstÀlla att uppdateringar pÄ samma sida Äterspeglas (eftersom `storage`-hÀndelser endast utlöses i andra flikar). serverSnapshot sÀkerstÀller att initiala vÀrden tillhandahÄlls korrekt i servermiljöer.
BĂ€sta praxis och vanliga fallgropar
HÀr Àr nÄgra bÀsta praxis och vanliga fallgropar att undvika nÀr du anvÀnder useSyncExternalStore:
- Undvik att mutera den externa storen direkt: AnvÀnd alltid storens API för att uppdatera datan. Att mutera storen direkt kan leda till inkonsekventa uppdateringar och ovÀntat beteende.
- SÀkerstÀll att
getSnapshotÀr ren och snabb:getSnapshotbör inte ha nÄgra sidoeffekter och ska returnera ett stabilt vÀrde snabbt. Dyra berÀkningar eller datatransformationer bör memoizeras. - TillhandahÄll en
getServerSnapshot-funktion nÀr du anvÀnder SSR: Detta Àr avgörande för att sÀkerstÀlla att den initiala HTML-koden som renderas pÄ servern matchar den klient-sidiga renderingen. - Hantera fel pÄ ett elegant sÀtt: AnvÀnd try-catch-block för att hantera potentiella fel nÀr du ansluter till den externa storen.
- StÀda upp prenumerationer: Avsluta alltid prenumerationen pÄ den externa storen nÀr komponenten avmonteras för att förhindra minneslÀckor.
subscribe-funktionen bör returnera en rensningsfunktion som tar bort lyssnaren. - FörstĂ„ prestandakonsekvenserna: Ăven om
useSyncExternalStoreÀr optimerad för prestanda, Àr det viktigt att förstÄ den potentiella inverkan av att prenumerera pÄ externa stores. Minimera mÀngden data som returneras avgetSnapshotoch undvik onödiga prenumerationer. - Testa noggrant: SÀkerstÀll att integrationen med storen fungerar korrekt i olika scenarier, sÀrskilt vid server-side rendering och i concurrent mode.
Slutsats
useSyncExternalStore Àr en kraftfull och effektiv hook för att integrera externa datakÀllor och state-hanteringsbibliotek i dina React-komponenter. Genom att förstÄ dess kÀrnkoncept, anvÀndningsmönster och bÀsta praxis kan du effektivt hantera delat state i dina React-applikationer och optimera prestandan. Oavsett om du integrerar med tredjepartsbibliotek, hanterar delat state över komponenter eller hÀmtar data frÄn externa API:er, erbjuder useSyncExternalStore en standardiserad och pÄlitlig lösning. AnvÀnd den för att bygga mer robusta, underhÄllbara och prestandastarka React-applikationer för en global publik.